package com.fzf.org.service.impl;

import lombok.extern.slf4j.Slf4j;

import cn.hutool.core.bean.BeanUtil;
import java.util.Date;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fzf.org.dao.entities.*;
import com.fzf.org.dao.mapper.*;
import com.fzf.org.dto.net.RedisInfoDTO;
import com.fzf.org.dto.req.admin.AdminLoginReqDTO;
import com.fzf.org.dto.req.admin.AdminUpdateReqDTO;
import com.fzf.org.dto.req.admin.TakedownReqDTO;
import com.fzf.org.dto.req.admin.UserBanReqDTO;
import com.fzf.org.dto.req.user.UserRegisterReqDTO;
import com.fzf.org.dto.resp.admin.*;
import com.fzf.org.dto.resp.blog.BlogDetailRespDTO;
import com.fzf.org.dto.resp.blog.BlogPageRespDTO;
import com.fzf.org.enums.AuditEnums;
import com.fzf.org.exception.BizException;
import com.fzf.org.exception.BizExceptionTypeEnum;
import com.fzf.org.page.PageRequest;
import com.fzf.org.page.PageResponse;
import com.fzf.org.service.AdminService;
import com.fzf.org.toolkit.*;
import com.fzf.org.enums.BlogStatusEnum;
import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;

import static com.fzf.org.toolkit.ConstantKey.FIXED_RATE;

@Slf4j
@Service
@RequiredArgsConstructor
public class AdminServiceImpl implements AdminService {

    private final static String ADMIN_PREFIX = "admin";

    private final AdminMapper adminMapper;
    private final UserMapper userMapper;
    private final BanedUserMapper banedUserMapper;
    private final AbnormalUserMapper abnormalUserMapper;
    private final BlogMapper blogMapper;
    private final AuditMapper auditMapper;


    private final FileStorageService fileStorageService;
    private final RedisOperateService redisOperateService;
    private final SliderCheckService sliderCheckService;


    @Override
    public Boolean checkAccountIfNormal(String account) {
        long failCount = redisOperateService.getFailCount(ADMIN_PREFIX, account);
        if (failCount > 3) {
            return false;
        }
        AdminDO admin = adminMapper.selectOne(new LambdaQueryWrapper<AdminDO>().eq(AdminDO::getAccount, account));
        return null == admin || admin.getNormal();
    }

    @Override
    public void uploadPicture(MultipartFile picture) {
        String originalFilename = picture.getOriginalFilename();
        String type = null;
        if (originalFilename != null) {
            type = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        }
        if ("png".equals(type) || "jpg".equals(type) || "jpeg".equals(type)) {
            String url = Optional.ofNullable(fileStorageService.of(picture).upload()).map(FileInfo::getUrl).orElseThrow(
                    () -> new BizException(BizExceptionTypeEnum.COS_UPLOAD_ERROR)
            );
            AdminDO admin = AdminDO.builder()
                    .id(BasicContext.getBasicId())
                    .pictureUrl(url)
                    .build();
            int update = adminMapper.updateById(admin);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }
        } else {
            throw new BizException(BizExceptionTypeEnum.NOT_ALLOWED_FILE_TYPE);
        }
    }

    @Override
    public AdminSelfRespDTO profile() {
        AdminDO admin = adminMapper.selectById(BasicContext.getBasicId());
        return BeanUtil.copyProperties(admin, AdminSelfRespDTO.class);
    }

    @Override
    public void updateAdminInfo(AdminUpdateReqDTO adminUpdateReqDTO) {
        AdminDO admin = BeanUtil.copyProperties(adminUpdateReqDTO, AdminDO.class);
        admin.setId(BasicContext.getBasicId());
        if (null != adminUpdateReqDTO.getPassword()) {
            admin.setOnline(false);
            admin.setPassword(adminUpdateReqDTO.getPassword());
            int update = adminMapper.updateById(admin);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }

            redisOperateService.deleteInfo(ADMIN_PREFIX, BasicContext.getBasicAccount());
            redisOperateService.removeFromLoginExpireZSet(ADMIN_PREFIX, BasicContext.getBasicAccount());
            redisOperateService.addTokenToBlackTokenZset(BasicContext.getAuthorizationToken());
        } else {
            int update = adminMapper.updateById(admin);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }
        }
    }

    // 检查本次登录请求是否是一个正确的滑块验证请求
    private boolean isSliderRequest(AdminLoginReqDTO userLoginReqDTO) {
        return StringUtils.hasText(userLoginReqDTO.getTicket()) &&
                StringUtils.hasText(userLoginReqDTO.getRandStr()) &&
                StringUtils.hasText(userLoginReqDTO.getUserIp());
    }

    private boolean checkSlider(AdminLoginReqDTO adminLoginReqDTO) {
        return sliderCheckService.checkSlider(adminLoginReqDTO.getTicket(),
                adminLoginReqDTO.getRandStr(),
                adminLoginReqDTO.getUserIp());
    }

    private String processAndGetToken(AdminDO admin) {
        if (null != redisOperateService.getInfo(ADMIN_PREFIX, admin.getAccount())) { // 禁止重复登录
            throw new BizException(BizExceptionTypeEnum.USER_REPEAT_LOGIN);
        }

        String JWTToken = JWTAuthenticator.generateToken(admin);

        admin.setOnline(true);
        admin.setLastLoginTime(new Date(System.currentTimeMillis()));
        int update = adminMapper.updateById(admin);// 修改用户登录状态
        if (update != 1) {
            throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
        }

        // 生成redis存储的对象信息实体
        RedisInfoDTO redisInfoDTO = BeanUtil.copyProperties(admin, RedisInfoDTO.class);
        redisInfoDTO.setToken(JWTToken);
        // 向redis新增包含用户信息的实体数据
        redisOperateService.setInfoWithExpireTIme(ADMIN_PREFIX, redisInfoDTO.getAccount(), redisInfoDTO);
        // 向redis的登录管理zset中插入已经登录的用户id
        redisOperateService.addToLoginExpireZset(ADMIN_PREFIX, redisInfoDTO.getAccount());
        return JWTToken;
    }


    @Override
    public String login(AdminLoginReqDTO adminLoginReqDTO) {
        if (isSliderRequest(adminLoginReqDTO)) { // 是否本次请求带有滑块验证
            if (checkSlider(adminLoginReqDTO)) { // 通过验证进入下一步数据库比对，否则视为错误请求
                AdminDO admin = adminMapper.selectOne(
                        new LambdaQueryWrapper<AdminDO>()
                                .eq(AdminDO::getAccount, adminLoginReqDTO.getAccount())
                );
                if (null == admin ||
                        !Argon2PasswordEncoder.matches(adminLoginReqDTO.getPassword(), admin.getPassword())) {
                    throw new BizException(BizExceptionTypeEnum.USER_ACCOUNT_OR_PWD_ERROR);
                } else { // 返回正确的 jwt token
                    return processAndGetToken(admin);
                }
            }
            throw new BizException(BizExceptionTypeEnum.USER_REQUEST_ERROR);
        } else {
            AdminDO admin = adminMapper.selectOne(
                    new LambdaQueryWrapper<AdminDO>()
                            .eq(AdminDO::getAccount, adminLoginReqDTO.getAccount())
                            .eq(AdminDO::getNormal, true)
            );
            if (null == admin ||
                    !Argon2PasswordEncoder.matches(adminLoginReqDTO.getPassword(), admin.getPassword())) {
                long count = redisOperateService.getAndSetFailCount(ADMIN_PREFIX, adminLoginReqDTO.getAccount());
                if (count == 3) {
                    int update = adminMapper.update(AdminDO.builder().normal(false).build(), new LambdaQueryWrapper<AdminDO>()
                            .eq(AdminDO::getAccount, adminLoginReqDTO.getAccount()));
                    if (update != 1) {
                        throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
                    }
                }
                throw new BizException(BizExceptionTypeEnum.USER_ACCOUNT_OR_PWD_ERROR);
            } else { // 返回正确的 jwt token
                return processAndGetToken(admin);
            }
        }
    }

    @Override
    public void logout() {
        AdminDO admin = AdminDO.builder().
                id(BasicContext.getBasicId())
                .online(false)
                .build();
        int update = adminMapper.updateById(admin);
        if (update != 1) {
            throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
        }

        redisOperateService.deleteInfo(ADMIN_PREFIX, BasicContext.getBasicAccount());
        redisOperateService.removeFromLoginExpireZSet(ADMIN_PREFIX, BasicContext.getBasicAccount());
        redisOperateService.addTokenToBlackTokenZset(BasicContext.getAuthorizationToken());
    }

    //-----------------------------------------------------------------------------------

    @Override
    public PageResponse<UserPageRespDTO> getUserInfoByPage(PageRequest pageRequest,
                                                           Boolean status, String searchQuery) {
        // 参数校验
        if (pageRequest == null) {
            throw new BizException(BizExceptionTypeEnum.PARAM_ERROR);
        }

        log.info("获取用户分页信息，当前页：{}，每页大小：{}，在线状态：{}，搜索关键词：{}",
                pageRequest.getCurrent(), pageRequest.getSize(), status, searchQuery);

        try {
            // 构建查询条件，实现可选的过滤
            Page<UserDO> resultPage = userMapper.selectPage(new Page<>(pageRequest.getCurrent(), pageRequest.getSize()),
                    new LambdaQueryWrapper<UserDO>()
                            // 当status不为null时，添加在线状态匹配条件
                            .eq(status != null, UserDO::getOnline, status)
                            // 当searchQuery不为空时，添加模糊查询条件
                            .and(StringUtils.hasText(searchQuery), wrapper -> wrapper
                                    .like(UserDO::getAccount, searchQuery)));

            // 防止records为null导致NPE
            List<UserDO> records = resultPage.getRecords();
            if (records == null) {
                log.warn("查询用户列表返回null，创建空列表");
                records = Collections.emptyList();
            }

            List<UserPageRespDTO> convertRecords = records.stream()
                    .map(userDO -> {
                        if (userDO == null) {
                            log.warn("用户列表中存在null元素，已跳过");
                            return null;
                        }
                        return BeanUtil.copyProperties(userDO, UserPageRespDTO.class);
                    })
                    .filter(Objects::nonNull)
                    .toList();

            return new PageResponse<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal(), convertRecords);
        } catch (Exception e) {
            log.error("获取用户分页信息失败", e);
            throw new BizException(BizExceptionTypeEnum.SYSTEM_ERROR);
        }
    }

    @Override
    public PageResponse<UserAbnormalPageRespDTO> getAbnormalUserByPage(PageRequest pageRequest) {
        Page<AbnormalUserDO> resultPage = abnormalUserMapper.selectPage(new Page<>(pageRequest.getCurrent(), pageRequest.getSize()),
                new LambdaQueryWrapper<AbnormalUserDO>());

        List<UserAbnormalPageRespDTO> convertRecords = resultPage.getRecords()
                .stream()
                .map(abnormalUserDO -> BeanUtil.copyProperties(abnormalUserDO, UserAbnormalPageRespDTO.class)).toList();

        return new PageResponse<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal(), convertRecords);
    }


    @Override
    public PageResponse<BlogPageRespDTO> getBlogsByPage(PageRequest pageRequest,
                                                        String name, String status, String searchQuery) {
        LambdaQueryWrapper<BlogDO> queryWrapper = new LambdaQueryWrapper<>();

        // 根据name查询用户ID
        if (StringUtils.hasText(name)) {
            UserDO user = userMapper.selectOne(new LambdaQueryWrapper<UserDO>()
                    .eq(UserDO::getName, name));
            if (user != null) {
                queryWrapper.eq(BlogDO::getUserId, user.getId());
            } else {
                // 如果用户不存在，直接返回空结果
                return new PageResponse<>(pageRequest.getCurrent(), pageRequest.getSize(), 0L, Collections.emptyList());
            }
        }

        if (StringUtils.hasText(status)) {
            queryWrapper.eq(BlogDO::getStatus, status);
        }

        if (StringUtils.hasText(searchQuery)) {
            queryWrapper.and(wrapper -> wrapper
                    .like(BlogDO::getTitle, searchQuery)
                    .or()
                    .like(BlogDO::getContent, searchQuery));
        }

        // 参数校验
        if (pageRequest == null) {
            throw new BizException(BizExceptionTypeEnum.PARAM_ERROR);
        }

        try {
            Page<BlogDO> resultPage = blogMapper.selectPage(
                    new Page<>(pageRequest.getCurrent(), pageRequest.getSize()), queryWrapper);

            // 防止records为null导致NPE
            List<BlogDO> records = resultPage.getRecords();
            if (records == null) {
                log.warn("查询博客列表返回null，创建空列表");
                records = Collections.emptyList();
            }

            List<BlogPageRespDTO> convertRecords = records.stream()
                    .map(blogDO -> {
                        if (blogDO == null) {
                            log.warn("博客列表中存在null元素，已跳过");
                            return null;
                        }
                        return BeanUtil.copyProperties(blogDO, BlogPageRespDTO.class);
                    })
                    .filter(Objects::nonNull)
                    .toList();

            return new PageResponse<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal(), convertRecords);
        } catch (Exception e) {
            log.error("获取博客分页信息失败", e);
            throw new BizException(BizExceptionTypeEnum.SYSTEM_ERROR);
        }
    }



    @Override
    public BlogDetailRespDTO getBlogDetail(Long blogId) {
        // 参数校验
        if (blogId == null) {
            throw new BizException(BizExceptionTypeEnum.PARAM_ERROR);
        }

        // 查询博客信息
        BlogDO blogDO = blogMapper.selectOne(new LambdaQueryWrapper<BlogDO>().eq(BlogDO::getId, blogId));
        if (blogDO == null) {
            throw new BizException(BizExceptionTypeEnum.BLOG_NOT_EXIST_ERROR);
        }

        // 获取用户信息
        UserDO userDO = userMapper.selectById(blogDO.getUserId());
        if (userDO == null) {
            throw new BizException(BizExceptionTypeEnum.USER_NOT_EXIST_ERROR);
        }

        // 拷贝属性
        BlogDetailRespDTO dto = BeanUtil.copyProperties(blogDO, BlogDetailRespDTO.class);
        dto.setUserId(userDO.getId());
        dto.setUserName(userDO.getName());

        // 如果是下架状态，查询下架原因
        if (blogDO.getStatus() != null && BlogStatusEnum.DOWN.getValue().equals(blogDO.getStatus())) {
            AuditDO auditDO = auditMapper.selectOne(new LambdaQueryWrapper<AuditDO>()
                    .eq(AuditDO::getBlogId, blogId)
                    .eq(AuditDO::getAction, AuditEnums.TAKEDOWN.getValue())
                    .orderByDesc(AuditDO::getCreateTime)
                    .last("LIMIT 1"));

            if (auditDO != null) {
                dto.setTakedownReason(auditDO.getReason());
            }
        }

        return dto;
    }

    @Override
    public boolean takedownBlog(TakedownReqDTO dto) {
        // 参数校验
        if (dto == null || dto.getBlogId() == null) {
            throw new BizException(BizExceptionTypeEnum.PARAM_ERROR);
        }

        // 检查博客是否存在
        BlogDO existBlog = blogMapper.selectById(dto.getBlogId());
        if (existBlog == null) {
            throw new BizException(BizExceptionTypeEnum.BLOG_NOT_EXIST_ERROR);
        }

        // 检查博客是否已下架
        if (BlogStatusEnum.DOWN.getValue().equals(existBlog.getStatus())) {
            log.info("博客已经是下架状态，无需重复操作，blogId={}", dto.getBlogId());
            return true; // 已下架状态，直接返回成功
        }

        // 更新博客状态
        BlogDO blogDO = BlogDO.builder()
                .id(dto.getBlogId())
                .status(BlogStatusEnum.DOWN.getValue())
                .updateTime(new Date())
                .build();
        int result = blogMapper.updateById(blogDO);

        // 添加审计记录
        if (result > 0) {
            try {
                Long operatorId = BasicContext.getBasicId();

                AuditDO auditDO = AuditDO.builder()
                        .blogId(dto.getBlogId())
                        .action(AuditEnums.TAKEDOWN.getValue())
                        .operatorId(operatorId)
                        .reason(dto.getReason())
                        .createTime(new Date())
                        .updateTime(new Date())
                        .build();
                auditMapper.insert(auditDO);
                log.info("博客下架成功，blogId={}, operatorId={}", dto.getBlogId(), operatorId);
            } catch (Exception e) {
                // 记录审计失败不影响下架操作
                log.error("记录下架审计失败，blogId={}, 原因：{}", dto.getBlogId(), e.getMessage());
            }
        } else {
            log.warn("博客下架失败，blogId={}", dto.getBlogId());
        }

        return result > 0;
    }

    @Override
    public Boolean applyAccount(UserRegisterReqDTO userRegisterReqDTO) {
        Long l = userMapper.selectCount(new LambdaQueryWrapper<UserDO>().eq(UserDO::getAccount, userRegisterReqDTO.getAccount()));
        if (l == 0) {
            UserDO userDO = UserDO.builder()
                    .account(userRegisterReqDTO.getAccount())
                    .password(Argon2PasswordEncoder.encode(userRegisterReqDTO.getPassword())) // argon2 对密码加密
                    .roleId(1L)
                    .phone(userRegisterReqDTO.getPhone())
                    .normal(true)
                    .build();
            int insert = userMapper.insert(userDO);
            if (insert != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_INSERT_ERROR);
            }
            return true;
        }
        throw new BizException(BizExceptionTypeEnum.USER_EXIST_ERROR);
    }

    @Override
    public void kickOutUser(String account) {
        RedisInfoDTO userInfo = redisOperateService.getInfo("user", account);//
        if (null == userInfo) {
            // 是否还需要额外判断呢？
        } else {
            UserDO user = UserDO.builder().id(userInfo.getId()).online(false).build();
            int update = userMapper.updateById(user);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }

            redisOperateService.deleteInfo("user", account);
            // 删除redis的登录管理zset中插入已经登录的用户id
            redisOperateService.removeFromLoginExpireZSet("user", account);
            // 将token放入黑名单中
            redisOperateService.addTokenToBlackTokenZset(userInfo.getToken());
        }
    }

    @Override
    public void banUser(UserBanReqDTO userBanReqDTO) {
        kickOutUser(userBanReqDTO.getAccount());

        Long l = banedUserMapper.selectCount(new LambdaQueryWrapper<BanedUser>().eq(BanedUser::getAccount, userBanReqDTO.getAccount()));
        if (l == 0) {
            BanedUser banedUser = BanedUser.builder()
                    .account(userBanReqDTO.getAccount())
                    .banedReason(userBanReqDTO.getBanDesc())
                    .roleId(1L)
                    .build();
            int insert = banedUserMapper.insert(banedUser);
            if(insert != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_BANED_INSERT_ERROR);
            }
        }
    }

    @Override
    public void unBanUser(String account) {
        Long l = banedUserMapper.selectCount(new LambdaQueryWrapper<BanedUser>().eq(BanedUser::getAccount, account));
        if (l != 0) {
            int delete = banedUserMapper.delete(new LambdaQueryWrapper<BanedUser>().eq(BanedUser::getAccount, account));
            if (delete != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_BANED_DELETE_ERROR);
            }
        }
    }

    @Override
    public UserDetailRespDTO getUserDetail(String account) {
        UserDO user = userMapper.selectOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getAccount, account));
        if (null == user) {
            throw new BizException(BizExceptionTypeEnum.USER_NOT_EXIST_ERROR);
        }
        return BeanUtil.copyProperties(user, UserDetailRespDTO.class);
    }

    @Scheduled(fixedDelay = FIXED_RATE) // 执行完一次后计时
    public void scheduledUpdateUserStatus() {
        System.out.println("admin-批量进行数据库更新状态操作");
        List<String> userAccounts = redisOperateService.getAndRemoveFromLoginExpireZSetByRange(ADMIN_PREFIX);
        if (null == userAccounts || userAccounts.isEmpty()) {// 如果不需要执行则返回，这样可以减少一次与数据库的交互
            return;
        }
        adminMapper.batchUpdateOnlineToFalse(userAccounts);
    }


    @Override
    public PageResponse<AuditRespDTO> getAuditByPage(PageRequest pageRequest) {
        if (pageRequest == null) {
            throw new BizException(BizExceptionTypeEnum.PARAM_ERROR);
        }

        Page<AuditDO> resultPage = auditMapper.selectPage(new Page<>(pageRequest.getCurrent(), pageRequest.getSize()), null);

        List<AuditRespDTO> convertRecords = resultPage.getRecords()
                .stream()
                .map(auditDO -> BeanUtil.copyProperties(auditDO, AuditRespDTO.class))
                .toList();

        return new PageResponse<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal(), convertRecords);
    }
}